﻿using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using GraphDrawing2.V2.Concrete.Model;

namespace GraphDrawing2.V2.Graph.Model
{
    public class ElementsModel
    {
        readonly Dictionary<IVisualElement, LinkedListNode<IVisualElement>> zOrderIndex = new Dictionary<IVisualElement, LinkedListNode<IVisualElement>>();

        /// <summary>
        /// Содержит элементы в порядке увеличения Z-индекса, то есть от самого удалённого от наблюдателя к самому близкому.
        /// </summary>
        readonly LinkedList<IVisualElement> zOrder = new LinkedList<IVisualElement>();

        /// <summary>
        /// Добавляет элемент на вершину Z-последовательности.
        /// </summary>
        /// <param name="element">Добавляемый элемент.</param>
        public void Add(IVisualElement element)
        {
            var node = zOrder.AddLast(element);
            zOrderIndex[element] = node;
            element.Owner = this;
        }

        /// <summary>
        /// Удаляет элемент из Z-последовательности.
        /// </summary>
        public void Remove(IVisualElement element)
        {
            LinkedListNode<IVisualElement> node;
            if (!zOrderIndex.TryGetValue(element, out node))
                return;
            element.Owner = null;
            zOrderIndex.Remove(element);
            zOrder.Remove(node);
        }

        /// <summary>
        /// Перемещает элемент на вершину Z-последовательности.
        /// </summary>
        public void BringToFront(IVisualElement element)
        {
            var node = zOrderIndex[element];
            zOrder.Remove(node);
            zOrder.AddLast(node);
        }

        public IHitTestResult HitTest(PointF point)
        {
            return EnumerateFromNearest().OfType<IInteractive>().Select(element => element.HitTest(point)).FirstOrDefault(result => result.Type != HitTestType.NONE) ?? NoneHitTest.INSTANCE;
        }

        public IEnumerable<IVisualElement> GetElements(EnumerationOrder order = EnumerationOrder.FromFurthestToNearest)
        {
            return order == EnumerationOrder.FromNearestToFurthest ? EnumerateFromNearest() : zOrder;
        }

        IEnumerable<IVisualElement> EnumerateFromNearest()
        {
            for (var node = zOrder.Last; node != null; node = node.Previous)
                yield return node.Value;
        }
    }

    public enum EnumerationOrder
    {
        FromNearestToFurthest,
        FromFurthestToNearest
    }
}